<?php

class DB {

	private static $_conns = array();

	private $_dbh;

	/**
	 * db 构造函数
	 *
	 * @param string $dsn
	 * @param string $user
	 * @param string $password
	 * @param string $charset
	 * @param string $failover
	 * @param boolean $persistent
	 * @param integer $timeout
	 * @param bool $setNames
	 * @throws Exception
	 */
	private function __construct($dsn, $user, $password, $charset, $failover = '', $persistent = false, $timeout = 0, $setNames = true) {
		$attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT, PDO::ATTR_PERSISTENT => $persistent);
		if (0 < $timeout) {
			$attr[PDO::ATTR_TIMEOUT] = $timeout;
		}

		try {
			$this->_dbh = new PDO($dsn, $user, $password, $attr);
			if ($setNames) {
				$this->_dbh->exec("SET NAMES '" . $charset . "'");
			}
		} catch (Exception $e) {
			if ($failover) {
				try {
					$this->_dbh = new PDO($failover, $user, $password, $attr);
					if ($setNames) {
						$this->_dbh->exec("SET NAMES '" . $charset . "'");
					}
				} catch (Exception $e) {
					throw new Exception("can't connect to the server because:" . $e->getMessage());
				}
			} else {
				throw new Exception("can't connect to the server because:" . $e->getMessage());
			}
		}
	}

	/**
	 * 获取数据库连接类
	 *
	 * @param string $dsn
	 * @param string $user
	 * @param string $password
	 * @param string $charset
	 * @param string $failover
	 * @param boolean $persistent
	 * @param integer $timeout
	 * @param bool $setNames
	 * @return DB 实例
	 * @throws Exception
	 */
	public static function connect($dsn, $user, $password, $charset, $failover = '', $persistent = false, $timeout = 0, $setNames = true) {
		$tryConn = false;
		if (!empty(self::$_conns[$dsn]) && PHP_SAPI == 'cli') {
			$info = self::$_conns[$dsn]->getAttr(PDO::ATTR_SERVER_INFO);
			if ($info == 'MySQL server has gone away') {
				$tryConn = true;
			}
		}

		if (!isset(self::$_conns[$dsn]) || $tryConn) {
			self::$_conns[$dsn] = new DB($dsn, $user, $password, $charset, $failover, $persistent, $timeout, $setNames);
		}

		return self::$_conns[$dsn];
	}

	/**
	 * 开启事物
	 *
	 * @return void
	 */
	public function begin() {
		$this->_dbh->beginTransaction();
	}

	/**
	 * 提交事务
	 *
	 * @return void
	 */
	public function commit() {
		$this->_dbh->commit();
	}

	/**
	 * 回滚事务
	 *
	 * @return void
	 */
	public function rollBack() {
		$this->_dbh->rollBack();
	}

	/**
	 * 取得记录的第一行
	 *
	 * @param $query
	 * @param array $params
	 * @return mixed
	 * @throws Exception
	 */
	public function fetchRow($query, $params = array()) {
		$stmt = $this->_dbh->prepare($query);
		$stmt->execute($params);
		$this->_checkError($stmt);
		$row = $stmt->fetch(PDO::FETCH_ASSOC);
		return $row;

	}

	/**
	 * 取得所有的记录
	 *
	 * @param string $query
	 * @param array $params
	 * @return array
	 * @throws Exception
	 */
	public function fetchAll($query, $params = array()) {
		$stmt = $this->_dbh->prepare($query);
		$stmt->execute($params);
		$this->_checkError($stmt);
		$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
		return $rows;
	}

	/**
	 * 获取记录的第一行第一列
	 *
	 * @param string $query
	 * @param array $params
	 * @return mixed
	 * @throws Exception
	 */
	public function fetchOne($query, $params = array()) {
		$stmt = $this->_dbh->prepare($query);
		$result = $stmt->execute($params);
		$this->_checkError($stmt);
		$row = 0;
		if ($result) {
			$row = $stmt->fetchColumn();
		}
		return $row;
	}

	/**
	 * 执行sql 语句
	 *
	 * @param string $query
	 * @param array $params
	 * @return bool
	 * @throws Exception
	 */
	public function exec($query, $params = array()) {
		$stmt = $this->_dbh->prepare($query);
		$return = $stmt->execute($params);
		$this->_checkError($stmt);
		return $return;
	}

	/**
	 * 获取最后一条记录的id
	 *
	 * @return string
	 * @author 黄磊 <huanglei@comsenz.com>
	 * @date 10/21/2009
	 */
	public function lastInsertId() {
		return $this->_dbh->lastInsertId();
	}

	/**
	 * 关闭数据库连接
	 * @param string|null $dsn
	 */
	public function close($dsn = null) {
		if ($dsn) {
			self::$_conns[$dsn]->close();
			unset(self::$_conns[$dsn]);
		} else {
			$this->_dbh = null;
		}
	}

	/**
	 * closeAll
	 * 关闭所有的链接
	 *
	 * @return true
	 */
	public static function closeAll() {
		if (self::$_conns) {
			foreach (self::$_conns as $key => $con) {
				self::$_conns[$key]->close();
				unset(self::$_conns[$key]);
			}
		}

		return true;
	}

	/**
	 * getAttr
	 * 获取连接属性
	 *
	 * @param $attr
	 * @return mixed
	 */
	public function getAttr($attr) {
		return $this->_dbh->getAttribute($attr);
	}

	/**
	 * _checkError
	 * 检测错误
	 *
	 * @param $stmt
	 * @return void
	 * @throws Exception
	 */
	private function _checkError(&$stmt) {
		if (!empty($GLOBALS['DB_SILENT'])) {
			return;
		}
		$code = intval($stmt->errorCode());
		if (!empty($code)) {
			$errInfo = $stmt->errorInfo();
			throw new Exception('<br />SQL: ' . $stmt->queryString .
				'<br />Error: ' . $errInfo[0] . ' ' . $errInfo[2], $errInfo[1]);
		}
	}

}
